using System;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using Xeno_Race.Game;
using Xeno_Race.GameScreens;
using Xeno_Race.Graphics;
using Xeno_Race.Helpers;
using Xeno_Race.SoundManager;

namespace Xeno_Race
{
    /// <summary>
    /// Main class of our game
    /// </summary>
    public class XenoGame : BaseGame
    {
        // TODO: Recode Input Handling to use our existing InputHelper Helper class!
        // (This actually goes to the us team which placed 
        // this redundant code here in the first place...)
        KeyboardState lastKeyboardState = new KeyboardState();
        GamePadState lastGamePadState = new GamePadState();
        KeyboardState currentKeyboardState = new KeyboardState();
        GamePadState currentGamePadState = new GamePadState();

        /// <summary>
        /// Game screens stack. We can easily add and remove game screens
        /// and they follow the game logic automatically.
        /// </summary>
        private Stack<IGameScreen> gameScreens = new Stack<IGameScreen>();

        /// <summary>
        /// Rectangles for the sprites, tested with unit test TestGameSprites()
        /// </summary>
        private static readonly Rectangle
            fadeRect = new Rectangle(0, 0, 1024, 286),
            logoRect = new Rectangle(0, 288, 770, 327),
            smallLogoRect = new Rectangle(0, 790, 540, 230),
            storyRect = new Rectangle(384, 0, 299, 560),
            linesRect = new Rectangle(0, 0, 376, 588),
            pressStartRect = new Rectangle(687, 315, 275, 45),
            singleplayerRect = new Rectangle(687, 0, 275, 45),
            multiplayerRect = new Rectangle(687, 45, 275, 45),
            highscoresRect = new Rectangle(687, 90, 275, 45),
            optionsRect = new Rectangle(687, 135, 275, 45),
            helpRect = new Rectangle(687, 180, 275, 45),
            creditsRect = new Rectangle(687, 225, 275, 45),
            exitRect = new Rectangle(687, 270, 275, 45),
            backRect = new Rectangle(687, 450, 275, 45),
            contRect = new Rectangle(687, 360, 275, 45),
            mainMenuRect = new Rectangle(687, 405, 275, 45),
            retryRect = new Rectangle(687, 540, 275, 45),
            controlRect = new Rectangle(0, 590, 688, 436),
            selectRect = new Rectangle(720, 588, 246, 246),
            racerRect = new Rectangle(0, 0, 236, 305),
            xerelRect = new Rectangle(0, 0, 236, 305),
            earthRect = new Rectangle(565, 627, 342, 342),
            moonRect = new Rectangle(776, 324, 248, 250),
            // HUD Rectangles
            whiteFrameRec = new Rectangle(0, 0, 264, 106),
            greyPlaneRec = new Rectangle(0, 110, 263, 98),
            hud0Rec = new Rectangle(135, 365, 37, 37),
            hud1Rec = new Rectangle(204, 327, 36, 38),
            hud2Rec = new Rectangle(21, 327, 36, 38),
            hud3Rec = new Rectangle(58, 327, 36, 38),
            hud4Rec = new Rectangle(95, 327, 36, 38),
            hud5Rec = new Rectangle(132, 327, 36, 38),
            hud6Rec = new Rectangle(168, 327, 36, 38),
            hud7Rec = new Rectangle(0, 365, 36, 37),
            hud8Rec = new Rectangle(36, 365, 36, 37),
            hud9Rec = new Rectangle(72, 365, 37, 37),
            hudColonRec = new Rectangle(109, 365, 13, 37),
            hudDotRec = new Rectangle(122, 365, 13, 37);
            
        /// <summary>
        /// Textures used
        /// </summary>
        public Texture2D
            backgroundTexture = null,
            logoTexture = null,
            menuTexture = null,
            collTrackTestTexture = null,
            trackWalls = null,
            XenoRacer = null,
            Xereldius = null,
            hudTexture = null;

        /// <summary>
        /// All sprites we will use, defined as sprite helpers
        /// </summary>
        public SpriteHelper
            backgroundImg,
            fadeImg,
            logoImg,
            smallLogoImg,
            storyImg,
            linesImg,
            pressStartButton,
            singleplayerButton,
            multiplayerButton,
            highscoresButton,
            optionsButton,
            helpButton,
            creditsButton,
            exitButton,
            backButton,
            continueButton,
            mainMenuButton,
            retryButton,
            XRacer,
            XBadman,
            circleSelect,
            earth,
            moon,
            controllerImg,
            whiteFrame,
            greyPlane,
            hud0,
            hud1,
            hud2,
            hud3,
            hud4,
            hud5,
            hud6,
            hud7,
            hud8,
            hud9,
            hudColon,
            hudDot;

        /// <summary>
        /// Input states
        /// </summary>
        public InputHelper currentInputState, previousInputState;

        /// <summary>
        /// Soundmanager
        /// </summary>
        protected Sound sounds;

        /// <summary>
        /// Sound manager access for other classes
        /// </summary>
        /// <returns>Sound manager</returns>
        public Sound Sounds
        {
            get { return sounds; }
        }

        /// <summary>
        /// Special xeno red color
        /// </summary>
        public Color xenoRed = new Color(110, 22, 36), fadeXenoRed;
        
        /// <summary>
        /// Color of sky
        /// </summary>
        public Color skyColor = new Color(45, 45, 99);

        /// <summary>
        /// Indicates if the game is in menu or displaying 3D
        /// </summary>
        static bool inGame = false;

        /// <summary>
        /// Gets/Sets inGame value
        /// </summary>
        /// <returns>Bool</returns>
        public bool InGame
        {
            get { return inGame; }
            set { inGame = value; }
        }
        
        /// <summary>
        /// Indicates if we play single or multi
        /// </summary>
        static bool multiPlayer = false;

        /// <summary>
        /// Gets/Sets multiplayer value
        /// </summary>
        /// <returns>Bool</returns>
        public bool MultiPlayer
        {
            get { return multiPlayer; }
            set { multiPlayer = value; }
        }

        /// <summary>
        /// Chasecam with spring behaviour
        /// </summary>
        private ChaseCamera camera, camera1, camera2;

        /// <summary>
        /// Tracks used
        /// </summary>
        private Track[] tracks = new Track[2];

        /// <summary>
        /// Access to tracks
        /// </summary>
        /// <returns>Ship</returns>
        public Track[] Tracks
        {
            get { return tracks; }
        }

        /// <summary>
        /// Class for the player ships
        /// </summary>
        public Ship[] ships = new Ship[3];

        /// <summary>
        /// Access to player 1 ship
        /// </summary>
        /// <returns>Ship</returns>
        public Ship PlayerOneShip
        {
            get { return ships[0]; }
        }

        /// <summary>
        /// Array with players
        /// </summary>
        private Player[] players = new Player[2];

        /// <summary>
        /// Player 1 access
        /// </summary>
        /// <returns>Player</returns>
        public Player Player
        {
            get { return players[0]; }
        }

        /// <summary>
        /// Player 2 access
        /// </summary>
        /// <returns>Player</returns>
        public Player Player2
        {
            get { return players[1]; }
        }

        #region Splitscreen vars
        /// <summary>
        /// Default viewport, for single game
        /// </summary>
        Viewport defaultViewport;

        /// <summary>
        /// Left viewport for multiplayer splitscreen game
        /// </summary>
        Viewport leftViewport;

        /// <summary>
        /// Right viewport for multiplayer splitscreen game
        /// </summary>
        Viewport rightViewport;
        #endregion

        /// <summary>
        /// Enables or disables spring behaviour of chasecam
        /// </summary>
        private bool cameraSpringEnabled = true;

        /// <summary>
        /// Enables or disables drawing of collision track if we are in debug mode
        /// </summary>
        private bool drawCollisionTrack = true;
        
        /// <summary>
        /// Enables or disables drawing of collision track
        /// </summary>
        public bool DrawCollisionTrack
        {
            set { drawCollisionTrack = value; }
        }

        /// <summary>
        /// Used to generate the fading red
        /// </summary>
        float fadeState;

        /// <summary>
        /// Default constructor
        /// </summary>
        public XenoGame()
        {
            // Create the chase cameras
            // default camera for singleplayer
            camera = new ChaseCamera();
            // camera1/2 for multiplayer splitscreen
            camera1 = new ChaseCamera();
            camera2 = new ChaseCamera();

            // Set the camera offsets
            camera.DesiredPositionOffset = new Vector3(0.0f, 1000.0f, 3500.0f);
            camera.LookAtOffset = new Vector3(0.0f, 150.0f, 0.0f);
            camera1.DesiredPositionOffset = new Vector3(0.0f, 1000.0f, 3500.0f);
            camera1.LookAtOffset = new Vector3(0.0f, 150.0f, 0.0f);
            camera2.DesiredPositionOffset = new Vector3(0.0f, 1000.0f, 3500.0f);
            camera2.LookAtOffset = new Vector3(0.0f, 150.0f, 0.0f);

            // Set camera perspective
            ChaseCamera.NearPlaneDistance = 10.0f;
            ChaseCamera.FarPlaneDistance = 1000000.0f;

            // Set Window Title
            this.Window.Title = "Xeno Race";            
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // Generate Players
            players[0] = new Player("Player One");
            players[1] = new Player("Player Two");
            players[1].ShipSelection = 1; // Give player 2 second ship right away

            // Load Sounds
            sounds = new Sound();
            sounds.LoadMenuSounds(base.Content);

            // For our main track load collision track from external data
            CollisionTrack collisionTrack = new CollisionTrack(loadTrackBasePoints("content//Trackdata//Track1CollPath",
                                                                    0, 2, 1, // Order of X, Y and Z Coordinates in source data
                                                                    0.25f, 0.25f, -0.25f // X, Y and Z multiplier, to prescale and mirror points
                                                                    ), 8000.0f, 0.56f); // Scaling and half trackwidth
            tracks[0] = new Track(collisionTrack);
            // Test track uses default collision track
            tracks[1] = new Track(new CollisionTrack(loadTrackBasePoints("content//Trackdata//Track2CollPath",
                                                                    0, 2, 1, // Order of X, Y and Z Coordinates in source data
                                                                    0.25f, 0.25f, -0.25f // X, Y and Z multiplier, to prescale and mirror points
                                                                    ), 8000.0f, 0.6f));

            // Create player ships and set required values
            ships[0] = new Ship(tracks[Player.TrackSelection].CollisionTrack, 500.0f, 490.0f, 80.0f, 4000000.0f);
            ships[0].EngineSound = sounds.getSoundEffect(SoundManager.Sound.Sounds.turbine_idle);
            ships[0].LapCompleted += new EventHandler(players[0].OnLapCompleted);
            ships[1] = new Ship(tracks[Player.TrackSelection].CollisionTrack, 500.0f, 490.0f, 80.0f, 4000000.0f);
            ships[1].EngineSound = sounds.getSoundEffect(SoundManager.Sound.Sounds.turbine_idle);
            ships[1].LapCompleted += new EventHandler(players[0].OnLapCompleted);
            ships[2] = new Ship(tracks[Player.TrackSelection].CollisionTrack);
            ships[2].EngineSound = sounds.getSoundEffect(SoundManager.Sound.Sounds.turbine_idle);
            ships[2].LapCompleted += new EventHandler(players[0].OnLapCompleted);

            // Create main menu screen
            gameScreens.Push(new Intro());

            base.Initialize();
            // Set the camera aspect ratio
            // This must be done after the class to base.Initalize() which will
            // initialize the graphics device.
            camera.AspectRatio = (float) base.Width / base.Height;
            camera1.AspectRatio = (float) (base.Width / 2) / base.Height;
            camera2.AspectRatio = (float) (base.Width / 2) / base.Height;

            // Perform an inital reset on the cameras so that they start at the resting
            // position. If we don't do this, the camera will start at the origin and
            // race across the world to get behind the chased object.
            // This is performed here because the aspect ratio is needed by Reset.
            UpdateCameraChaseTarget();
            camera.Reset();
            camera1.Reset();
            camera2.Reset();

            // Sets basic effect of Track (required to draw trackwalls)
            Track.BasicEffect = new BasicEffect(GraphicsDevice, new EffectPool());
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Load all our content
            // Textures
            XenoRacer = content.Load<Texture2D>("content//textures//Xereldius");
            Xereldius = content.Load<Texture2D>("content//textures//XenoRacer");
            backgroundTexture = Content.Load<Texture2D>("content//textures//background");
            logoTexture = Content.Load<Texture2D>("content//textures//logo");
            menuTexture = Content.Load<Texture2D>("content//textures//menu");
            hudTexture = Content.Load<Texture2D>("content//textures//hud");

            trackWalls = Content.Load<Texture2D>("content//textures//Leitplanke");
            // Set wall texture
            tracks[0].WallTexture = trackWalls;
            tracks[1].WallTexture = trackWalls;

            // Simple texture for drawing collision track only used for testing
            collTrackTestTexture = Content.Load<Texture2D>("content//textures//collTrackTexture");

            // Create all menu sprites
            fadeImg = new SpriteHelper(logoTexture, fadeRect);
            logoImg = new SpriteHelper(logoTexture, logoRect);
            smallLogoImg = new SpriteHelper(logoTexture, smallLogoRect);
            storyImg = new SpriteHelper(menuTexture, storyRect);
            linesImg = new SpriteHelper(menuTexture, linesRect);
            pressStartButton = new SpriteHelper(menuTexture, pressStartRect);
            singleplayerButton = new SpriteHelper(menuTexture, singleplayerRect);
            multiplayerButton = new SpriteHelper(menuTexture, multiplayerRect);
            highscoresButton = new SpriteHelper(menuTexture, highscoresRect);
            optionsButton = new SpriteHelper(menuTexture, optionsRect);
            helpButton = new SpriteHelper(menuTexture, helpRect);
            creditsButton = new SpriteHelper(menuTexture, creditsRect);
            exitButton = new SpriteHelper(menuTexture, exitRect);
            backButton = new SpriteHelper(menuTexture, backRect);
            continueButton = new SpriteHelper(menuTexture, contRect);
            mainMenuButton = new SpriteHelper(menuTexture, mainMenuRect);
            retryButton = new SpriteHelper(menuTexture, retryRect);
            controllerImg = new SpriteHelper(menuTexture, controlRect);
            XRacer = new SpriteHelper(XenoRacer, racerRect);
            XBadman = new SpriteHelper(Xereldius, xerelRect);
            circleSelect = new SpriteHelper(menuTexture, selectRect);
            earth = new SpriteHelper(logoTexture, earthRect);
            moon = new SpriteHelper(logoTexture, moonRect);
            whiteFrame = new SpriteHelper(hudTexture, whiteFrameRec);
            greyPlane = new SpriteHelper(hudTexture, greyPlaneRec);
            hud0 = new SpriteHelper(hudTexture, hud0Rec);
            hud1 = new SpriteHelper(hudTexture, hud1Rec);
            hud2 = new SpriteHelper(hudTexture, hud2Rec);
            hud3 = new SpriteHelper(hudTexture, hud3Rec);
            hud4 = new SpriteHelper(hudTexture, hud4Rec);
            hud5 = new SpriteHelper(hudTexture, hud5Rec);
            hud6 = new SpriteHelper(hudTexture, hud6Rec);
            hud7 = new SpriteHelper(hudTexture, hud7Rec);
            hud8 = new SpriteHelper(hudTexture, hud8Rec);
            hud9 = new SpriteHelper(hudTexture, hud9Rec);
            hudColon = new SpriteHelper(hudTexture, hudColonRec);
            hudDot = new SpriteHelper(hudTexture, hudDotRec);

            // Finally create the background texture as a sprite, too
            backgroundImg = new SpriteHelper(backgroundTexture, null);

            // Models of player ships
            ships[0].UsedModel = Content.Load<Model>("content//models//ship1");
            ships[1].UsedModel = Content.Load<Model>("content//models//ship2");
            ships[2].UsedModel = Content.Load<Model>("content//models//Ship3"); // TODO for Americans: implement your model in menus!

            // Ground models of tracks
            tracks[0].GroundModel = Content.Load<Model>("content//models//01_destroyed_earth_v1.0");
            tracks[1].GroundModel = Content.Load<Model>("content//models//TestGround");

            // Create viewports for single and multi game
            defaultViewport = GraphicsDevice.Viewport;
            leftViewport = defaultViewport;
            rightViewport = defaultViewport;
            leftViewport.Width = leftViewport.Width / 2;
            rightViewport.Width = rightViewport.Width / 2;
            rightViewport.X = leftViewport.Width + 1;
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // Everything is already unloaded in the BaseGame class
            base.UnloadContent();
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            if (inGame)
            {
                // TODO: Recode Input Handling to use our InputHelper Helper class!
                lastKeyboardState = currentKeyboardState;
                lastGamePadState = currentGamePadState;

                currentKeyboardState = Keyboard.GetState();
                currentGamePadState = GamePad.GetState(PlayerIndex.One);

                // Pressing the B button or key toggles the spring behavior on and off
                if (lastKeyboardState.IsKeyUp(Keys.B) &&
                    (currentKeyboardState.IsKeyDown(Keys.B)) ||
                    (lastGamePadState.Buttons.B == ButtonState.Released &&
                    currentGamePadState.Buttons.B == ButtonState.Pressed))
                {
                    cameraSpringEnabled = !cameraSpringEnabled;
                }
                
                // Pressing the left shoulder button or v key toggles displaying collision track
                if (lastKeyboardState.IsKeyUp(Keys.V) &&
                    (currentKeyboardState.IsKeyDown(Keys.V)) ||
                    (lastGamePadState.Buttons.LeftShoulder == ButtonState.Released &&
                    currentGamePadState.Buttons.LeftShoulder == ButtonState.Pressed))
                {
                    if (!multiPlayer)
                        drawCollisionTrack = !drawCollisionTrack;
                }

                // Pressing the right shoulder button or c key toggles track collision checks
                if (lastKeyboardState.IsKeyUp(Keys.C) &&
                    (currentKeyboardState.IsKeyDown(Keys.C)) ||
                    (lastGamePadState.Buttons.RightShoulder == ButtonState.Released &&
                    currentGamePadState.Buttons.RightShoulder == ButtonState.Pressed))
                {
                    if (!multiPlayer)
                        ships[Player.ShipSelection].toggleCollisions();
                }

                // Reset the ship on R key or right thumb stick clicked
                if (currentKeyboardState.IsKeyDown(Keys.R) ||
                    currentGamePadState.Buttons.RightStick == ButtonState.Pressed)
                {
                    Reset();
                }

                // Update the ship
                ships[Player.ShipSelection].Update(gameTime);
                // Update second ship also in multiplayer and check for collisions
                if (multiPlayer)
                {
                    ships[Player2.ShipSelection].Update(gameTime);
                    checkShipCollision();
                }

                foreach (Player p in players)
                {
                    p.PlayingTime += gameTime.ElapsedGameTime.TotalMilliseconds;
                }

                // Update the camera to chase the new target
                UpdateCameraChaseTarget();

                // The chase camera's update behavior is the springs, but we can
                // use the Reset method to have a locked, spring-less camera
                if (multiPlayer)
                {
                    if (cameraSpringEnabled)
                    {
                        camera1.Update(gameTime);
                        camera2.Update(gameTime);
                    }
                    else
                    {
                        camera1.Reset();
                        camera2.Reset();
                    }
                }
                else
                {
                    if (cameraSpringEnabled)
                        camera.Update(gameTime);
                    else
                        camera.Reset();
                }
            } // if (inGame)


            // get previous and current input states
            previousInputState = currentInputState;
            currentInputState = InputHelper.GetState();

            // Allows the game to exit when pressing back or Esc
            // Only works on the first two screens (intro and main menu)
            if (currentInputState.ExitButton && !previousInputState.ExitButton && gameScreens.Count <= 2)
                this.Exit();

            // Update our fading color
            fadeXenoRed = getFadeState(gameTime, new Color(183, 139, 146), xenoRed);

            // If a game screen should be quitted, remove it from stack!
            if (gameScreens.Count > 0 &&
                gameScreens.Peek().Quit)
                RemoveCurrentGameScreen();

            // If no more game screens are left, it is time to quit!
            if (gameScreens.Count == 0)
            {
                this.Exit();
            } // if

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            if (inGame)
            {
                // Clear device
                base.Device.Clear(skyColor);
                
                // Important settings for 3D
                // Make sure alpha blending is enabled.
                base.Device.RenderState.AlphaBlendEnable = true;
                base.Device.RenderState.SourceBlend = Blend.SourceAlpha;
                base.Device.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
                // Enable Depthbuffer
                base.Device.RenderState.DepthBufferEnable = true;
                base.Device.RenderState.DepthBufferWriteEnable = true;

                // TODO: Add sky
                // base.skyCube.RenderSky(1.0f, base.remSkyBackgroundColor);

                if (multiPlayer)
                {
                    // Default (full screen)
                    base.Device.Viewport = defaultViewport;

                    // Left (left part of screen)
                    base.Device.Viewport = leftViewport;
                    // Draw our models
                    DrawModel(ships[Player.ShipSelection].UsedModel, ships[Player.ShipSelection].World, camera1);
                    DrawModel(ships[Player2.ShipSelection].UsedModel, ships[Player2.ShipSelection].World, camera1);
                    DrawModel(tracks[Player.TrackSelection].GroundModel, Matrix.Identity, camera1);

                    // Draw collision stuff
                    if (drawCollisionTrack)
                    {
                        // Draws normal track walls and collision track data for debugging
                        tracks[Player.TrackSelection].Draw(ships[Player.ShipSelection], camera1, collTrackTestTexture);
                    }
                    else
                    {
                        // Only draw walls based on collision track
                        tracks[Player.TrackSelection].Draw(camera1);
                    }


                    // Right (right part of screen)
                    base.Device.Viewport = rightViewport;
                    // Draw our models
                    DrawModel(ships[Player2.ShipSelection].UsedModel, ships[Player2.ShipSelection].World, camera2);
                    DrawModel(ships[Player.ShipSelection].UsedModel, ships[Player.ShipSelection].World, camera2);
                    DrawModel(tracks[Player.TrackSelection].GroundModel, Matrix.Identity, camera2);

                    // Draw collision stuff
                    if (drawCollisionTrack)
                    {
                        // Draws normal track walls and collision track data for debugging
                        tracks[Player.TrackSelection].Draw(ships[Player2.ShipSelection], camera2, collTrackTestTexture);
                    }
                    else
                    {
                        // Only draw walls based on collision track
                        tracks[Player.TrackSelection].Draw(camera2);
                    }

                    base.Device.Viewport = defaultViewport;
                }
                else
                {
                    // Draw our models
                    DrawModel(ships[Player.ShipSelection].UsedModel, ships[Player.ShipSelection].World, camera);
                    DrawModel(tracks[Player.TrackSelection].GroundModel, Matrix.Identity, camera);

                    // Draw collision stuff
                    if (drawCollisionTrack)
                    {
                        // Draws normal track walls and collision track data for debugging
                        tracks[Player.TrackSelection].Draw(ships[Player.ShipSelection], camera, collTrackTestTexture);
                    }
                    else
                    {
                        // Only draw walls based on collision track
                        tracks[Player.TrackSelection].Draw(camera);
                    }
                }

                // Disable z buffer, now only 2d content is rendered.
                base.Device.RenderState.DepthBufferEnable = false;
            }

            // This gets the gamescreen on top of our stack and renders it
            try
            {
                // Execute the game screen on top.
                if (gameScreens.Count > 0)
                    gameScreens.Peek().Run(this);
            }
            catch (Exception ex)
            {
                Log.Write("Failed to execute " + gameScreens.Peek().Name +
                    "\nError: " + ex.ToString());
            }
#if DEBUG

            if (!ships[Player.ShipSelection].CollisionsEnabled)
                TextureFont.WriteText(1024 - 240, 2, "Collisions off");
#endif
            base.Draw(gameTime);
        }

        /// <summary>
        /// Helper method to draw our models
        /// </summary>
        private void DrawModel(Model model, Matrix world, ChaseCamera camera)
        {
            Matrix[] transforms = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(transforms);
            
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    // Default light
                    effect.LightingEnabled = true;
                    effect.AmbientLightColor = new Vector3(0.1f, 0.1f, 0.1f);
                    effect.PreferPerPixelLighting = true;

                    Vector3 lightDirection = new Vector3(6, -10, 2);
                    lightDirection.Normalize();
                    effect.DirectionalLight0.Direction = lightDirection;
                    effect.DirectionalLight0.DiffuseColor = Color.White.ToVector3();
                    effect.DirectionalLight0.Enabled = true;
                    effect.DirectionalLight1.Enabled = false;
                    effect.DirectionalLight2.Enabled = false;

                    effect.World = transforms[mesh.ParentBone.Index] * world;

                    // Use the matrices provided by the chase camera
                    effect.View = camera.View;
                    effect.Projection = camera.Projection;
                    
                    // Makes the mesh transparent/invisible
                    // effect.Alpha  = 0.5f;
                }
                // if (model.Meshes.Count > 1)
                // Device.RenderState.FillMode = FillMode.WireFrame;
                mesh.Draw();
                // Device.RenderState.FillMode = FillMode.Solid;
            }
        }

        /// <summary>
        /// Update cam
        /// </summary>
        private void UpdateCameraChaseTarget()
        {
            camera.ChasePosition = ships[Player.ShipSelection].Position;
            camera.ChaseDirection = ships[Player.ShipSelection].Direction;
            camera.Up = ships[Player.ShipSelection].Up;

            camera1.ChasePosition = ships[Player.ShipSelection].Position;
            camera1.ChaseDirection = ships[Player.ShipSelection].Direction;
            camera1.Up = ships[Player.ShipSelection].Up;

            camera2.ChasePosition = ships[Player2.ShipSelection].Position;
            camera2.ChaseDirection = ships[Player2.ShipSelection].Direction;
            camera2.Up = ships[Player2.ShipSelection].Up;
        }

        /// <summary>
        /// Check the two ships for collisions and handle them
        /// </summary>
        private void checkShipCollision()
        {
            // We simplify the two ships to two balls with the radius r1/r2
            // and check if the distance between the two ships is below r1+r2
            Vector3 distance = ships[Player.ShipSelection].Position - ships[Player2.ShipSelection].Position;
            float r1 = ships[Player.ShipSelection].Radius,
                r2 = ships[Player2.ShipSelection].Radius,
                distanceAbs = (float)Math.Sqrt(distance.LengthSquared());

            // Woha personal bubbles invaded!
            // http://www.youtube.com/watch?v=xD-Huwlg2kY :)
            if (distanceAbs < (r1 + r2))
            {
                Vector3 distanceN = Vector3.Normalize(distance);
                
                // Move ships a bit away from each other
                float moveBack = (((r1 + r2) - distanceAbs) / 2) * 1.1f;
                ships[Player.ShipSelection].Position += distanceN * moveBack;
                ships[Player2.ShipSelection].Position += -distanceN * moveBack;
                
                // Get masses
                float m1 = ships[Player.ShipSelection].Mass,
                    m2 = ships[Player2.ShipSelection].Mass;

                // Calculate velocity vectors
                Vector3 v1, v2, v1Normal, v2Normal, v1Tangent, v2Tangent;
                v1 = ships[Player.ShipSelection].Velocity;
                v2 = ships[Player2.ShipSelection].Velocity;

                // Dotproduct with normalized distancevector gives us the 
                // lenght of the speed vector projected along the distance.
                // We multiply this with the normalized distance vector again 
                // to get the speed component towards the other ship
                v1Normal = distanceN * Vector3.Dot(distanceN, v1);
                v2Normal = distanceN * Vector3.Dot(distanceN, v2);

                // Get force along collision tangent (stays equal before and after coll.)
                v1Tangent = v1 - v1Normal;
                v2Tangent = v2 - v2Normal;

                // TODO: Get formulas working with two ships having different mass!
                // v1Normal = ((m1 - m2) * v1Normal + (2 * m2 * v2Normal)) / (m1 + m2);
                // v2Normal = ((m2 - m1) * v2Normal + (2 * m1 * v1Normal)) / (m1 + m2);

                // Simply switch impuls along collision normal since both ships have identical masses
                v1 = v2Normal + v1Tangent;
                v2 = v1Normal + v2Tangent;

                // Slow down a bit
                v1 *= 0.8f;
                v2 *= 0.8f;

                // Apply new speeds to ships
                ships[Player.ShipSelection].Velocity = v1;
                ships[Player2.ShipSelection].Velocity = v2;

                // Done...
                // It's neither pretty nor realistic but it works as a very simple 
                // collision behaviour between the two ships! Hope its fun in multiplayer...
            }
        }
        
        /// <summary>
        /// Loads the base points for a collision track from an xml file
        /// </summary>
        private List<Vector3> loadTrackBasePoints(String xmlFile, int x, int y, int z, float xMulti, float yMulti, float zMulti)
        {
            List<Vector3> trackBasePoints = new List<Vector3>();

            String xmlData = Content.Load<string>(xmlFile);
            xmlData = xmlData.Trim();
            String[] lines = xmlData.Split('\n');

            // Gets a NumberFormatInfo associated with the en-US culture.
            NumberFormatInfo nfi = new CultureInfo("en-US", false).NumberFormat;

            for (int i = 0; i < lines.Length; i++)
            {
                String[] currLineCoords = lines[i].Split(' ');
                trackBasePoints.Add(new Vector3((float.Parse(currLineCoords[x], nfi) * xMulti),
                                                (float.Parse(currLineCoords[y], nfi) * yMulti),
                                                (float.Parse(currLineCoords[z], nfi) * zMulti)));
            }
           
            return trackBasePoints;
        }

        /// <summary>
        /// Generate a color that fades between the two input colors
        /// </summary>
        private Color getFadeState(GameTime gameTime, Color c1, Color c2)
        {
            // fps independent value
            float factorPerSecond = 0.5f *
                (float)gameTime.ElapsedRealTime.TotalMilliseconds / 200.0f;

            fadeState += factorPerSecond;
            if (fadeState > (2 * Math.PI))
                fadeState = 0;
            float fadeCos = (float)((Math.Sin(fadeState) + 1) / 2);

            byte a = (byte)(c1.A + (c2.A - c1.A) * fadeCos);
            byte r = (byte)(c1.R + (c2.R - c1.R) * fadeCos);
            byte g = (byte)(c1.G + (c2.G - c1.G) * fadeCos);
            byte b = (byte)(c1.B + (c2.B - c1.B) * fadeCos);

            return new Color(r, g, b, a);
        }


        #region Render Character selection images
        ///<summary>
        ///Renders the two pictures for our character selection
        ///</summary>
        public void RenderCharacterPictures()
        {
            XRacer.Render(222, 270);
            XBadman.Render(700, 270);
        }
        #endregion

        #region Render background
        /// <summary>
        /// Renders background and game title for menus
        /// </summary>
        public void RenderMenuBackground()
        {
            // Clear viewport
            base.Device.Clear(Color.Black);

            // Set and draw sprites
            fadeImg.Render(0, 0);
            backgroundImg.Render();

            forceSpriteDraw();
        }
        #endregion

        /// <summary>
        /// Resets ship and camera
        /// </summary>
        public void Reset()
        {
            ships[Player.ShipSelection].Reset();
            ships[Player2.ShipSelection].Reset();

            UpdateCameraChaseTarget();

            camera.Reset(true);
            camera1.Reset(true);
            camera2.Reset(true);
        }

        /// <summary>
        /// Switch ship of given player
        /// </summary>
        public void switchShip(int player, int ship)
        {
            players[player].ShipSelection = ship;
            // Make shure the ship is using current track data
            ships[players[player].ShipSelection].UpdateTrackData(
                tracks[players[player].TrackSelection].CollisionTrack);
            Reset();
        }

        /// <summary>
        /// Switch ship of first player
        /// </summary>
        public void switchShip(int ship)
        {
            switchShip(0, ship);
        }

        /// <summary>
        /// Switch used track of given player
        /// </summary>
        public void switchTrack(int player, int track)
        {
            players[player].TrackSelection = track;
            // Update used ship with new track data
            ships[players[player].ShipSelection].UpdateTrackData(
                tracks[players[player].TrackSelection].CollisionTrack);
            Reset();
        }

        /// <summary>
        /// Switch used track of first player
        /// </summary>
        public void switchTrack(int track)
        {
            switchTrack(0, track);
        }

        #region Game Screen handling
        /// <summary>
        /// Add game screen, which will be used until we quit it or add
        /// another game screen on top of it.
        /// </summary>
        /// <param name="newGameScreen">New game screen</param>
        public void AddGameScreen(IGameScreen newGameScreen)
        {
            gameScreens.Push(newGameScreen);

            // Set inGame indicator var to true if the new game screen is of the type
            // SingleGame or MultiGame (the two screens we actually play the game)
            inGame = (newGameScreen.GetType() == typeof(SingleGame) || newGameScreen.GetType() == typeof(MultiGame));
            multiPlayer = newGameScreen.GetType() == typeof(MultiGame);
            ships[Player.ShipSelection].InGameStatus = inGame;
            ships[Player2.ShipSelection].InGameStatus = inGame;
            ships[Player2.ShipSelection].SecondShip = multiPlayer;
            camera.MultiPlayer = multiPlayer;
            camera1.MultiPlayer = multiPlayer;
            camera2.MultiPlayer = multiPlayer;
        } // AddGameScreen(newGameScreen)

        /// <summary>
        /// Remove current game screen
        /// </summary>
        public void RemoveCurrentGameScreen()
        {
            gameScreens.Pop();

            // Check if we are still ingame?
            inGame = gameScreens.Count > 0 &&
                (gameScreens.Peek().GetType() == typeof(SingleGame) || gameScreens.Peek().GetType() == typeof(MultiGame));
            multiPlayer = gameScreens.Peek().GetType() == typeof(MultiGame);
            // If we switch from inGame to menu stop the ship engine sound
            ships[Player.ShipSelection].InGameStatus = inGame;
            ships[Player2.ShipSelection].InGameStatus = inGame;
            ships[Player2.ShipSelection].SecondShip = multiPlayer;
            camera.MultiPlayer = multiPlayer;
            camera1.MultiPlayer = multiPlayer;
            camera2.MultiPlayer = multiPlayer;
        } // RemoveCurrentGameScreen()
        #endregion

        #region Start game
        /// <summary>
        /// Start game
        /// </summary>
        public static void StartGame()
        {
            using (XenoGame game = new XenoGame())
            {
                game.Run();
            } // using (game)
        } // StartGame()
        #endregion

        #region Unit tests
#if DEBUG
        #region Test delegate
        /// <summary>
        /// Test delegate helper
        /// </summary>
        delegate void TestDelegate();
        #endregion

        #region TestXenoGame class
        /// <summary>
        /// Helper class to test the game
        /// </summary>
        /// <param name="setTestLoop">Set test loop</param>
        class TestXenoGame : XenoGame
        {
            /// <summary>
            /// Test loop
            /// </summary>
            TestDelegate testLoop;
            public TestXenoGame(TestDelegate setTestLoop)
            {
                testLoop = setTestLoop;
            }

            /// <summary>
            /// Draw
            /// </summary>
            /// <param name="gameTime">Game time</param>
            protected override void Draw(GameTime gameTime)
            {
                base.Draw(gameTime);
                testLoop();
            } // Draw(gameTime)
        }
        #endregion

        #region StartTest
        /// <summary>
        /// Test game
        /// </summary>
        static TestXenoGame testGame;
        /// <summary>
        /// Start test
        /// </summary>
        /// <param name="testLoop">Test loop</param>
        static void StartTest(TestDelegate testLoop)
        {
            testGame = new TestXenoGame(testLoop);
            testGame.Run();
            testGame.Dispose();
        } // StartTest(testLoop)
        #endregion

        #region TestGameSprites
        /// <summary>
        /// Test game sprites
        /// </summary>
        public static void TestGameSprites()
        {
            StartTest(
                delegate
                {
                    testGame.fadeImg.Render(0, 0);
                    testGame.logoImg.Render(36, 22);
                    testGame.storyImg.Render(714, 188);
                    testGame.linesImg.Render(0, 180);
                    //testGame.pressStartButton.Render(382, 580);
                    int i = 0;
                    testGame.singleplayerButton.Render(382, 180 + (48 * i++));
                    testGame.multiplayerButton.Render(382, 180 + (48 * i++));
                    testGame.highscoresButton.Render(382, 180 + (48 * i++));
                    testGame.optionsButton.Render(382, 180 + (48 * i++));
                    testGame.helpButton.Render(382, 180 + (48 * i++));
                    testGame.creditsButton.Render(382, 180 + (48 * i++));
                    testGame.exitButton.Render(382, 180 + (48 * i++));
                    testGame.pressStartButton.Render(382, 180 + (48 * i++), testGame.xenoRed);
                    testGame.continueButton.Render(382, 180 + (48 * i++));
                    testGame.mainMenuButton.Render(382, 180 + (48 * i++));
                    testGame.backButton.Render(382, 180 + (48 * i++));
                    testGame.retryButton.Render(382, 180 + (48 * i++));
                    testGame.backgroundImg.Render();
                    // SpriteHelper.DrawSprites(testGame.Width, testGame.Height);
                    TextureFont.WriteText(100, 100, "Screen pos");
                });
        } // TestGameSprites()
        #endregion

        #region Test3DGame
        /// <summary>
        /// Test 3D game (collision, physics...)
        /// </summary>
        public static void Test3DGame()
        {
            StartTest(
                delegate
                {
                    testGame.RemoveCurrentGameScreen();
                    testGame.AddGameScreen(new SingleGame());
                });
        } // Test3DGame()
        #endregion
#endif
        #endregion
    }
}
